/*
 * simulador.java
 *
 * Created on 19 de Marco de 2008, 15:20
 * 
 */
package Simulador;

import Simulador.Event.Evento;
import Simulador.Job.Segmento;
import Auxiliar.FilaEventos;
import Simulador.Event.InfoEventoArquivo;
import Simulador.Hardware.Arquivo;
import Simulador.Hardware.CPU;
import Simulador.Hardware.Disco;
import Simulador.Hardware.Impressora;
import Simulador.Hardware.Memoria;
import Simulador.Job.Processo;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;


/**
 * Classe principal do Simulador. Controla os dispositivos e faz o tratamento
 * dos eventos.
 * @author Fernando
 * @author Matheus
 */
public class simulador {

    /**
     * Prioridades são usadas na hora de imprimir os resultados na tela. 
     */
    public static final int prioridadeSaida = 0;
    public static final int prioridadeDebug1 = 1;
    static final int prioridade = 1;//Maximo nivel de prioridade impresso.
    /**
     * Tipos de eventos a serem impressos na tela.
     * Caso true, eventos do tipo sao impressos
     * Caso false, mensagens sao ignoradas.
     */
    static final boolean NovoProcesso = true,
            AlocaMemoria = true,
            RequereCPU = true,
            ProxProcesso = true,
            RequereLeituraArquivo = true,
            RequereEscritaArquivo = true,
            FimIOArquivo = true,
            FimProcesso = true,
            RequereIODisco = true,
            FimIODisco = true,
            inicializacao = false;
    static final boolean debugCPU = true,
            debugJob = true;
    public static final int INICIALIZACAO = 0,
            DEBUG_CPU = -1,
            DEBUG_JOB = -2;
    
    
    public static final float MAX_TEMPO_JOB = Float.POSITIVE_INFINITY;//Mudar
    /**
     * Tempos de tratamento de eventos.
     */
    static final float TEMPO_EVENTO_NOVO_PROCESSO = 0.01f,
            TEMPO_ALOCA_MEMORIA_OK = 0.021f,
            TEMPO_FILA_MEMORIA = 0.022f,
            TEMPO_REQUISICAO_CPU = 0.03f,
            TEMPO_TROCA_PROCESSO = 0.04f,
            TEMPO_REQUERE_IO_DISPOSITIVO = 0.05f,
            TEMPO_FIM_IO_DISPOSITIVO = 0.06f,
            TEMPO_FIM_PROCESSO = 0.07f,
            TEMPO_REQUISICAO_ARQUIVO = 0.08f,//Leitura/Escrita
            TEMPO_FIM_IO_ARQUIVO = 0.09f,
            TEMPO_FIM_JOB = 0.10f;

    private FilaEventos fEventos,  fEventosPassados;
    private CPU cpu;
    private Memoria mem;
    private Disco discos[];
    private Impressora imp; // Não utilizada.
    private static float tempoAtual,  /*maxTempoPorProcesso,*/  tempoFinal;
    private Processo jobs[];
    private float tempoMax;
    private int ultimoIdSegmento;
    private float timeSlice = 2;
    
    private boolean firstProcess;
    //Trocar MaxTempoPorJob por 1 tempo maximo em cada Job?

    public simulador(String path) {
        fEventos = new FilaEventos();
        fEventosPassados = new FilaEventos();
        cpu = new CPU();

        tempoAtual = 0;
        //maxTempoPorProcesso = 50000f;
        tempoFinal = Float.POSITIVE_INFINITY;
        inicializaSimulador(path);
        tempoMax = 0;
        firstProcess = false;
    }

    public static void main(String args[]) {
        simulador s = new simulador("./simulacao1/");
        s.simulacao();
    }

    private void simulacao() {
        printEvento("Comeco da Simulacao:", INICIALIZACAO, prioridadeSaida);
        while (tempoAtual <= tempoFinal && !fEventos.isEmpty()) {
            Evento prox = (Evento) fEventos.getItem();
            trataEvento(prox);
            fEventosPassados.addItem(prox); //Listagem completa dos eventos ocorridos durante a simulacao.
        }
        if (fEventos.isEmpty()) {
            System.out.println("Fim");
        }
        return;
    }

    private void inicializaSimulador(String path) {
        File f = new File(path + "hardware.cfg");
        FileInputStream fis;
        BufferedInputStream bis;
        BufferedReader dis;
        int numeroDeJobs = 0;
        int numeroDeDiscos = 0;
        int numeroDeImpressoras = 0;
        
        /* Pega Dados do Hardware e Jobs */
        try {
            fis = new FileInputStream(f);
            bis = new BufferedInputStream(fis);
            dis = new BufferedReader(new InputStreamReader(bis));
            try {
                /* Pega dados de Hardware */
                mem = new Memoria(Integer.decode(dis.readLine()));
                numeroDeDiscos = Integer.decode(dis.readLine());
                numeroDeImpressoras = Integer.decode(dis.readLine());
                dis.readLine();//Numero de Eventos Iniciais - Nao Implementar Ainda

                numeroDeJobs = Integer.decode(dis.readLine());
                printEvento("Dados Hardware:", INICIALIZACAO, prioridadeSaida);
                printEvento("  Memoria:" + mem.tamanho(), INICIALIZACAO, prioridadeSaida);
                printEvento("  Discos:" + numeroDeDiscos , INICIALIZACAO, prioridadeSaida);
                printEvento("  Impressoras:" + numeroDeImpressoras, INICIALIZACAO, prioridadeSaida);
                printEvento("  Jobs:" + numeroDeJobs, INICIALIZACAO, prioridadeSaida);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        /* Pega informacao dos Jobs e adiciona na fila de eventos */
        ultimoIdSegmento = 0;
        inicializaProcessos(path + "job/", numeroDeJobs);
        /* Pega informacao dos Discos */
        inicializaDiscos(path + "Disco/", numeroDeDiscos);
        /* Pega informação das Impressoras */
        inicializaImpressoras(path + "Impressora/", numeroDeImpressoras);
    /* FIM - Pega Dados do Hardware e Jobs */
    }
    //TODO atualizar - colocar Disco.
    private void inicializaSegmentos(String path, int numProc, Processo j) {
        File f;
        FileInputStream fis;
        BufferedInputStream bis;
        BufferedReader dis;

        while (numProc > 0) {
            f = new File(path + (numProc) + ".cfg");
            try {
                fis = new FileInputStream(f);
                bis = new BufferedInputStream(fis);
                dis = new BufferedReader(new InputStreamReader(bis));
                try {
                    // TODO Colocar na ordem certa.
                    int memProc = Integer.decode(dis.readLine());
                    int totalTime = Integer.decode(dis.readLine());
                    int totalEscritas = Integer.decode(dis.readLine());
                    int totalLeituras = Integer.decode(dis.readLine());
                    int arqId = Integer.decode(dis.readLine());
                    
                    j.addSeg(new Segmento(numProc, totalTime, memProc, totalEscritas, totalLeituras, arqId));
                    
                    printEvento("  Proc:" + numProc, INICIALIZACAO, prioridadeSaida);
                    printEvento("    mem:" + memProc, INICIALIZACAO, prioridadeSaida);
                    printEvento("    tempoCPU:" + totalTime , INICIALIZACAO, prioridadeSaida);
                    printEvento("    totalIO:" + totalEscritas, INICIALIZACAO, prioridadeSaida);
                    printEvento("", INICIALIZACAO, prioridadeSaida);
                    
                    numProc--;

                } catch (IOException e) {
                    e.printStackTrace();
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    private void inicializaProcessos(String path, int numJobs) {
        File f;
        FileInputStream fis;
        BufferedInputStream bis;
        BufferedReader dis;

        jobs = new Processo[numJobs];

        for (int i = 0; i < numJobs; i++) {
            f = new File(path + (i + 1) + ".cfg");
            try {
                fis = new FileInputStream(f);
                bis = new BufferedInputStream(fis);
                dis = new BufferedReader(new InputStreamReader(bis));
                try {

                    int totalTime = Integer.decode(dis.readLine());
                    int momentoDeChegada = Integer.decode(dis.readLine());
                    int numProc = Integer.decode(dis.readLine());

                    jobs[i] = new Processo(i + 1, totalTime, numProc, momentoDeChegada);

                    printEvento("Job:" + (i + 1), INICIALIZACAO, prioridadeSaida);
                    printEvento("  tempoCPU:" + totalTime, INICIALIZACAO, prioridadeSaida);
                    printEvento("", INICIALIZACAO, prioridadeSaida);
                    
                    inicializaSegmentos(path + (i + 1)+ "/", numProc, jobs[i]);
                    Evento E = new Evento(Evento.NovoSegmento, jobs[i].getSeg(), momentoDeChegada);
                    printEvento("  Evento: Momento:" + momentoDeChegada , INICIALIZACAO, prioridadeSaida);
                    printEvento("    JobId:" + (i), INICIALIZACAO, prioridadeSaida);
                    printEvento("    Tipo:" + Evento.NovoSegmento, INICIALIZACAO, prioridadeSaida);
                    printEvento("\n", INICIALIZACAO, prioridadeSaida);
                    
                    
                    fEventos.addItem(E);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    private void inicializaDiscos(String path, int numDiscos) {
        File f;
        FileInputStream fis;
        BufferedInputStream bis;
        BufferedReader dis;
        
        discos = new Disco[numDiscos];
        
        for (int i = 0; i < numDiscos;i++) {
            f = new File(path + (numDiscos) + ".cfg");
            try {
                fis = new FileInputStream(f);
                bis = new BufferedInputStream(fis);
                dis = new BufferedReader(new InputStreamReader(bis));
                try {
                    int tamDisco = Integer.decode(dis.readLine());
                    int vel = Integer.decode(dis.readLine());
                    int cab = Integer.decode(dis.readLine());
                    int numArquivos = Integer.decode(dis.readLine());
                    
                    discos[i] = new Disco(numDiscos,tamDisco, cab, vel, numArquivos);
                    //inicializaArquivos(path + i + "/", numArquivos, discos[i]);//desnecessario por enquanto
                    printEvento("Disco: " + (i + 1) + " - Adicionado", INICIALIZACAO, prioridadeSaida);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
    
    private void inicializaImpressoras(String path, int numImpressoras) {
        File f;
        FileInputStream fis;
        BufferedInputStream bis;
        BufferedReader dis;

        while (numImpressoras > 0) {
            f = new File(path + (numImpressoras) + ".cfg");
            try {
                fis = new FileInputStream(f);
                bis = new BufferedInputStream(fis);
                dis = new BufferedReader(new InputStreamReader(bis));
                try {
                    int vel = Integer.decode(dis.readLine());
                    boolean ligado = true;
                    if (Integer.decode(dis.readLine()) == 0) {
                        ligado = false;
                    }

                    imp = new Impressora(vel, ligado);
                    numImpressoras--;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
    }


    //TODO finalizar processos
    /*TODO Refazer as seguintes rotinas de tratamento de Eventos:
             eventoRequereIODisco
             eventoFimIODisco
             eventoFimProcesso
    */ 
    private void trataEvento(Evento E) {
        float t = 0.0f;
        if (E.getMomentoDeChegada() >= tempoAtual) {
            tempoAtual = E.getMomentoDeChegada();
        } else {
            E.setMomentoDeChegada(tempoAtual);
        }
        cpu.pausarSegmento(tempoAtual);
        switch (E.getTipoEvento()) {
            case Evento.NovoSegmento:
                t = eventoNovoSegmento(E);
                printEvento("Segmento:" + E.getSegmento().getId() + "Processo:" + E.getSegmento().getIdProcesso(), Evento.NovoSegmento ,prioridadeSaida);
                break;
            case Evento.AlocaMemoria:
                printEvento("Segmento:" + E.getSegmento().getId() + "Processo:" + E.getSegmento().getIdProcesso(), Evento.AlocaMemoria ,prioridadeSaida);
                t = eventoAlocaMemoria(E);
                break;
            case Evento.RequereCPU:
                printEvento("Segmento:" + E.getSegmento().getId() + "Processo:" + E.getSegmento().getIdProcesso(), Evento.RequereCPU ,prioridadeSaida);
                t = eventoRequereCPU(E);
                break;
            case Evento.ProxSegmento:
                printEvento("Proximo Segmento;", Evento.ProxSegmento ,prioridadeSaida);
                t = eventoProxSegmento(E);
                break;
            case Evento.RequereLeituraArquivo:
                printEvento("Segmento:" + E.getSegmento().getId() + "Processo:" + E.getSegmento().getIdProcesso(), Evento.RequereLeituraArquivo ,prioridadeSaida);
                t = eventoRequereArquivo(E);
                break;
            case Evento.RequereEscritaArquivo:
                printEvento("Segmento:" + E.getSegmento().getId() + "Processo:" + E.getSegmento().getIdProcesso(), Evento.RequereEscritaArquivo ,prioridadeSaida);
                t = eventoRequereArquivo(E);
                break;
            case Evento.FimIOArquivo:
                printEvento("Segmento:" + E.getSegmento().getId() + "Processo:" + E.getSegmento().getIdProcesso(), Evento.FimIOArquivo ,prioridadeSaida);
                t = eventoFimIOArquivo(E);
                throw new UnsupportedOperationException("Not Implemented Yet");
                //break;
            case Evento.RequereIODisco:
                printEvento("Segmento:" + E.getSegmento().getId() + "Processo:" + E.getSegmento().getIdProcesso(), Evento.RequereIODisco ,prioridadeSaida);
                t = eventoRequereIODisco(E);
                throw new UnsupportedOperationException("Not Implemented Yet");
                //break;
            case Evento.FimIODisco:
                printEvento("Segmento:" + E.getSegmento().getId() + "Processo:" + E.getSegmento().getIdProcesso(), Evento.FimIODisco ,prioridadeSaida);
                t = eventoFimIODisco(E);
                throw new UnsupportedOperationException("Not Implemented Yet");
                //break;
            case Evento.FimSegmento:
                printEvento("Segmento:" + E.getSegmento().getId() + "Processo:" + E.getSegmento().getIdProcesso(), Evento.FimSegmento ,prioridadeSaida);
                t = eventoFimSegmento(E);
                throw new UnsupportedOperationException("Not Implemented Yet");
                //break;
            default:
                System.out.print("\n\n\nErro - Evento Inexistente!!!\n\n\n");
                throw new Error("Evento Inexistente: " + E.getTipoEvento());
        }
        tempoAtual += t;
        cpu.continuarSegmento(tempoAtual, tempoMax);
    }

    private float eventoNovoSegmento(Evento E) {
        /* Tratamento dos eventos do Tipo 1: Chegada de Processos */
        /* Adiciona o Evento de Requisicao de Memoria(Tipo 2) no Instante Atual */
        E.getSegmento().setId(ultimoIdSegmento);
        ultimoIdSegmento++;
        fEventos.addItem(new Evento(Evento.AlocaMemoria, E.getSegmento(), tempoAtual));
        return TEMPO_EVENTO_NOVO_PROCESSO;
    }

    private float eventoAlocaMemoria(Evento E) {
        /*Tratamento dos eventos de Pedido de Alocacao de Memoria*/
        Evento e = mem.requerirRecurso(E);
        if (e != null) {
            fEventos.addItem(e);
            return TEMPO_ALOCA_MEMORIA_OK;
        } else {
            return TEMPO_FILA_MEMORIA;
        }
    }

    private float eventoRequereCPU(Evento E) {
        if(firstProcess == false){
            tempoMax = tempoAtual + timeSlice;
            fEventos.addItem(new Evento(Evento.ProxSegmento, null, tempoMax));
            firstProcess = true;
        }
        return TEMPO_REQUISICAO_CPU + cpu.adicionarSegmento(E.getSegmento());
    }

    private float eventoProxSegmento(Evento E) {
        tempoMax = tempoAtual + timeSlice;
        fEventos.addItem(new Evento(Evento.ProxSegmento, null, tempoMax));
        return TEMPO_TROCA_PROCESSO + cpu.executarProximoSegmento(tempoAtual, tempoMax);
    }

    /**
     * Trata evento de Requisicao de leitura de arquivo.
     * Pega o Id do Disco, busca o Arquivo e faz a requisicao.
     * @param E
     * Evento a ser tratado
     * @return
     * tempo necessario para a execucao da requisicao.
     */
    private float eventoRequereArquivo(Evento E) {
        InfoEventoArquivo ia = (InfoEventoArquivo)E.getInfo();
        int arqId = ia.getArquivoId();
        int discoId = ia.getDiscoId();
        Arquivo a = discos[discoId].getArquivo(arqId);
        Evento e = a.requerirRecurso(E);
        if(e != null){
            fEventos.addItem(e);
        }
        return TEMPO_REQUISICAO_ARQUIVO;
    }

    /**
     * Libera o Arquivo para outras operacoes.
     * @param E
     * Evento sendo tratado.
     * @return
     * Tempo de tratamento do evento.
     */
    private float eventoFimIOArquivo(Evento E) {
        InfoEventoArquivo ia = (InfoEventoArquivo)E.getInfo();
        int arqId = ia.getArquivoId();
        int discoId = ia.getDiscoId();
        Arquivo a = discos[discoId].getArquivo(arqId);
        FilaEventos fe = a.liberarRecurso(E);
        while(!fe.isEmpty()){
            fEventos.addItem(fe.getItem());
        }
        return TEMPO_FIM_IO_ARQUIVO;
    }

    private float eventoRequereIODisco(Evento E) {
        InfoEventoArquivo ia = (InfoEventoArquivo)E.getInfo();
        Evento e = discos[ia.getDiscoId()].requerirRecurso(E);
        if(e!=null){
            fEventos.addItem(e);
        }
        return TEMPO_REQUERE_IO_DISPOSITIVO;
    }
    
    private float eventoFimIODisco(Evento E) {
        //FimIOArquivo
        //
        throw new UnsupportedOperationException("Not yet implemented");
    }

    //TODO Reformular o Evento INTEIRO para aceitar Jobs/Processo ao inves de Processos somente.
    private float eventoFimSegmento(Evento E) {
        throw new UnsupportedOperationException("Not yet implemented");
        //return TEMPO_FIM_PROCESSO;
    }



    /**
     * Imprime ou nao a mensagem baseado na prioridade e no tipo de evento sendo tratado.
     * Util para fazer debug de partes do sistema.
     * @param str
     * Mensagem a ser exibida.
     * @param tipoEvento
     * Tipo do Evento que causou a mensagem
     * @param prioridade
     * Prioridade da mensagem
     */
    public static void printEvento(String str, int tipoEvento, int prioridade) {
        
        if (prioridade <= simulador.prioridade) {
            String s;
            if(prioridade >= prioridadeDebug1){
                s = new String("Debug:");
            }else{
                s = new String("");
            }
            switch (tipoEvento) {
                case Evento.AlocaMemoria:
                    if (AlocaMemoria) {
                        System.out.println( s + "Evento: AlocaMemoria; Momento:" + tempoAtual + "; " + str);
                    }
                    break;
                case Evento.FimIOArquivo:
                    if (FimIOArquivo) {
                        System.out.println( s + "Evento: FimIOArquivo; Momento:" + tempoAtual + "; " + str);
                    }
                    break;
                case Evento.FimIODisco:
                    if (FimIODisco) {
                        System.out.println( s + "Evento:   FimIODisco; Momento:" + tempoAtual + "; " + str);
                    }
                    break;
                case Evento.FimSegmento:
                    if (FimProcesso) {
                        System.out.println( s + "Evento:  FimProcesso; Momento:" + tempoAtual + "; " + str);
                    }
                    break;
                case Evento.NovoSegmento:
                    if (NovoProcesso) {
                        System.out.println( s + "Evento: NovoProcesso; Momento:" + tempoAtual + "; " + str);
                    }
                    break;
                case Evento.ProxSegmento:
                    if (ProxProcesso) {
                        System.out.println( s + "Evento: ProxProcesso; Momento:" + tempoAtual + "; " + str);
                    }
                    break;
                case Evento.RequereCPU:
                    if (RequereCPU) {
                        System.out.println( s + "Evento:   RequereCPU; Momento:" + tempoAtual + "; " + str);
                    }
                    break;
                case Evento.RequereEscritaArquivo:
                    if (RequereEscritaArquivo) {
                        System.out.println( s + "Evento: RequereEscritaArquivo; Momento:" + tempoAtual + "; " + str);
                    }
                    break;
                case Evento.RequereIODisco:
                    if (RequereIODisco) {
                        System.out.println( s + "Evento: RequereIODisco; Momento:" + tempoAtual + "; " + str);
                    }
                    break;
                case Evento.RequereLeituraArquivo:
                    if (RequereLeituraArquivo) {
                        System.out.println( s + "Evento: RequereLeituraArquivo; Momento:" + tempoAtual + "; " + str);
                    }
                    break;
                case INICIALIZACAO:
                    if(inicializacao){
                        System.out.println( s + "Inicializacao: " + str);
                    }
                break;
                case DEBUG_CPU:
                    if(debugCPU){
                        System.out.println(s +" CPU: "+ str);
                    }
                break;
                case DEBUG_JOB:
                    if(debugJob){
                        System.out.println(s +" Job: "+ str);
                    }
                break;
                default:
                    System.out.println("Erro");
            }
        }
    }
}